import { h, Teleport, reactive, Fragment, DefineComponent, RendererNode, Component } from "vue";
import { emits as eetEmits, props as eetProps } from './ee-animation'
import { props as popupProps } from './ee-popup'
import { props as dialogProps } from './ee-dialog'
import { props as loadingProps } from './ee-loading'
import eeDialog from './ee-dialog.vue'
import eeLoading from './ee-loading.vue'
import eeToast from './ee-toast.vue'
import eeNotify from './ee-notify.vue'
import eeAlert from './ee-alert.vue'
import eeShape from '../display/ee-shape.vue'
import eeSvgLoading from '../display/ee-svg-loading.vue'
import unique from '../__use/unique'
import hook from '../basic/ee-hook.vue'
import { fc } from '../index'

/** 弹窗props以外的选项 */
const popupOption = {
  /** 同Teleport组件的to参数，参考文档https://vuejs.org/api/built-in-components.html#teleport */
  teleport: '',
  /** 弹窗组件mounted触发，this可以获得弹窗组件的实例 */
  onMounted() { },
  /** 弹窗组件unmounted触发 */
  onUnmounted() { },
  /** 可以在弹窗组件内进行渲染或替换Slot内容，参考文档https://vuejs.org/guide/extras/render-function.html#passing-slots
   * @type {object.<string, function():RendererNode>}
   */
  render: { default: () => '渲染Slot' },
}

/** 弹窗实例
 * @typedef {object} popupInstance
 * @property {string | DefineComponent} component - 需要弹出的组件或组件名
 * @property {eetEmits | eetProps | popupProps} [options] - 组件渲染传递的props
 * @property {popupOption} [config] - 弹窗额外选项
 * @property {function(boolean)} close - 关闭弹窗的方法，参数传true可以立即关闭不用等待动画
 * @property {DefineComponent} instance - 弹窗组件实例，关闭后组件实例为空
 */

/** 弹窗函数
 * @callback popupMethod
 * @param {string | DefineComponent} component - 需要弹出的组件或组件名
 * @param {eetEmits | eetProps | popupProps} [options] - 组件渲染传递的props
 * @param {popupOption} [config] - 弹窗额外选项
 * @returns {popupInstance} 弹窗对象实例，可用于关闭弹窗
 */

/** 渲染一个弹窗组件 
 * @param {popupInstance} i
 */
function render(i) {
  return h(hook, {
    // 确保循环渲染的组件为同一个实例
    key: i.options.ref,
    onMounted() {
      const instance = this.$parent.$refs[i.options.ref];
      if (!instance.close)
        instance.close = i.close;
      i.instance = instance;
      i.config.onMounted?.call(instance);
    },
    onUnmounted() {
      i.config.onUnmounted?.();
    }
  }, [h(i.component, i.options, i.config.render)]);
}
/** 管理渲染所有弹窗的组件 */
const c = {
  data() {
    return {
      /** 所有的弹窗组件
       * @type {popupInstance[]} */
      popups: [],
    }
  },
  mounted() { i = this; },
  methods: {
    /** 无法删除$refs里的引用，那就重复利用这些引用 */
    getRef() {
      for (const key in this.$refs) {
        const value = this.$refs[key];
        if (!value && !this.popups.some(i => i.options.ref === value))
          return key;
      }
      return unique();
    }
  },
  render() {
    return h(
      Fragment,
      // 渲染所有的弹窗组件
      this.popups.map((i) => {
        if (!i.options.ref)
          i.options.ref = this.getRef();
        if (i.config.teleport) {
          return h(Teleport, {
            to: i.config.teleport
          }, render(i))
        }
        return render(i);
      })
    );
  },
};
/** 管理渲染所有弹窗的组件的唯一实例
 * @type {{ popups: popupInstance[] }}
 */
let i;

/** 基于ee-popup组件的弹窗
 * 1. 从大选项中抽出popupOption
 * 2. modelValue赋值为true
 * @param {popupOption} option
 * @returns {popupOption}
 */
function getPopupOption(option) {
  const o = {};
  for (const key in popupOption) {
    o[key] = option[key];
    delete option[key];
  }
  if (option.modelValue == undefined)
    option.modelValue = true;
  return o;
}

/** 对话框选项
 * @typedef {dialogProps | eetEmits | eetProps | popupProps | popupOption} dialogOption
 */
/** 对话框扩展选项
 * @typedef {string | dialogOption} dialogOption2
 */
/** 对话框API
 * @callback dialogAPI
 * @param {dialogOption2} option - 对话框选项，动态属性可以配合ee-vuex.createStore使用
 * @returns {Promise<boolean>} 对话框选择的结果，确定为true，取消和遮罩关闭为false
 */
/** 将对话框选项构建成dialogAPI类型
 * @param {dialogOption2} option
 * @returns {dialogOption}
 */
function fixDialogOption(option) {
  if (option.constructor == String)
    option = {
      text: option,
      confirm: true,
    }
  return option;
}
/** 弹出对话框
 * @type {dialogAPI}
 */
export function dialog(option) {
  option = fixDialogOption(option);
  let _i;
  let _r;
  const onOut = option.onOut;
  option.onOut = function () {
    _i.close();
    _r(_i.instance.result);
    onOut?.apply(_i.instance, arguments);
  }
  return new Promise(r => {
    _r = r;
    _i = m.open(eeDialog, option, getPopupOption(option));
  })
}
function dialogType(option, color, char) {
  option = fixDialogOption(option);
  if (!option.render) option.render = {};
  option.render.icon = () => {
    return h('div', {
      class: 'iconfont icon fc',
      style: {
        'background-color': color,
      }
    }, [char])
  }
  return dialog(option);
}
/** @type {dialogAPI} */
dialog.info = option => dialogType(option, 'var(--primary0)', 'i');
/** @type {dialogAPI} */
dialog.warning = option => dialogType(option, 'var(--warning0)', '!');
/** @type {dialogAPI} */
dialog.error = option => dialogType(option, 'var(--danger0)', h(eeShape, { style: 'font-size: .7em', shape: 'close' }));
/** @type {dialogAPI} */
dialog.success = option => dialogType(option, 'var(--success0)', h(eeShape, { style: 'font-size: .7em', shape: 'tick' }));

/** 加载框选项
 * @typedef {fc | loadingProps | eetEmits | eetProps | popupProps | popupOption} loadingOption
 */
/** 加载框精简选项
 * @typedef {boolean | Promise | loadingOption} loadingOption2
 * Boolean: 代表是否显示加载
 * 
 * Promise: 代表加载直到Promise结束
 * 
 * Object: wait同上面的Promise
 * 
 * 有Promise时，wait不生效
 */
/** 将加载框选项构建成loadingOption类型
 * @param {loadingOption2} option
 * @returns {loadingOption}
 */
export function fixLoadingOption(option) {
  if (!option)
    option = {};
  else if (option.coonstructor == Boolean)
    option = { modelValue: option };
  else if (option.constructor == Promise)
    option = { wait: option };
  return option;
}
/** 加载框API
 * @param {loadingOption2} option -
 * @returns {popupInstance}
 * @example
 * 动态属性可以配合ee-vuex.createStore使用
 * 使用ee-vuex
 * loading(createStore({
 *   countdown: [function () {
 *     setInterval(() => {
 *       this.countdown--;
 *     }, 1000)
 *     return 5;
 *   }],
 *   title() { return `请认真阅读条款，剩余${this.countdown}s` },
 *   duration: 5000,
 * }))
 * 
 * 这样会多一个countdown到attribute上，也可以尝试下面这种做法
 * 
 * createStore({
 *   text: {
 *     default() {
 *       let time = 5;
 *       const countdown = () => {
 *         const text = `还剩${time}秒`;
 *         this.text = text;
 *         if (time-- > 0)
 *           setTimeout(countdown, 1000)
 *         return text;
 *       }
 *       return countdown();
 *     }
 *   },
 *   duration: 5000
 * })
 * 
 * 不使用ee-vuex
 * let countdown = 5;
 * const popup = loading({})
 * const cd = () => {
 *   // 需要对popup.options中的属性赋值才有响应
 *   popup.options.title = `请认真阅读条款，剩余${countdown}s`
 *   countdown--;
 *   return cd;
 * }
 * setInterval(cd(), 1000)
 */
export function loading(option) {
  option = fixLoadingOption(option);
  const onOut = option.onOut;
  option.onOut = function () {
    _i.close(true);
    onOut?.apply(_i.instance, arguments);
  }
  const _i = m.open(eeLoading, option, getPopupOption(option));
  return _i;
}

function fixToastOption(option) {
  if (option == undefined)
    option = {};
  else if (option.constructor == String)
    option = {
      text: option,
      // duration: 0
    }
  if (!Object.hasOwn(option, 'scrollable'))
    option.scrollable = true;
  return option;
}
/** 弹出提示框
 * @type {dialogAPI}
 */
export function toast(option) {
  option = fixToastOption(option);
  const onOut = option.onOut;
  option.onOut = function () {
    _i.close(true);
    onOut?.apply(_i.instance, arguments);
  }
  const _i = m.show(eeToast, option, getPopupOption(option));
  return _i;
}
function toastType(option, char) {
  option = fixToastOption(option);
  if (!option.render) option.render = {};
  option.render.icon = () => {
    return h('div', {
      class: 'icon',
    }, [char])
  }
  return toast(option);
}
/** @type {dialogAPI} */
toast.info = option => toastType(option, 'i');
/** @type {dialogAPI} */
toast.warning = option => toastType(option, '!');
/** @type {dialogAPI} */
toast.error = option => toastType(option, h(eeShape, { shape: 'close' }));
/** @type {dialogAPI} */
toast.success = option => toastType(option, h(eeShape, { shape: 'tick' }));
/** @type {dialogAPI} */
toast.loading = option => {
  option = fixToastOption(option);
  if (!Object.hasOwn(option, 'overlay'))
    option.overlay = 3;
  return toastType(option, h(eeSvgLoading))
}
// todo: Toast应该只有一个实例，重复渲染也只是一个实例

/**
 * @param {string | dialogAPI} api - api展示弹出框
 * @param {function(dialogOption2):dialogOption2} fix - 处理option参数
 * @param {Component} component
 * @returns {dialogAPI} - 返回api对象
 */
function buildAPI(api, fix, component) {
  if (!api && component) {
    api = function (option) {
      option = fix(option);
      const onOut = option.onOut;
      option.onOut = function () {
        _i.close(true);
        onOut?.apply(_i.instance, arguments);
      }
      const _i = m.open(component, option, getPopupOption(option));
      return _i;
    }
    api.closeAll = function () {
      m.closeAll(component);
    }
  }
  const apiType = function (option, color, char) {
    option = fix(option);
    if (!option.render) option.render = {};
    option.render.icon = () => {
      return h('div', {
        class: 'icon',
        style: {
          'background-color': color,
        }
      }, [char])
    }
    return api(option);
  }
  /** @type {dialogAPI} */
  api.info = option => apiType(option, 'var(--primary0)', 'i');
  /** @type {dialogAPI} */
  api.warning = option => apiType(option, 'var(--warning0)', '!');
  /** @type {dialogAPI} */
  api.error = option => apiType(option, 'var(--danger0)', h(eeShape, { shape: 'close' }));
  /** @type {dialogAPI} */
  api.success = option => apiType(option, 'var(--success0)', h(eeShape, { shape: 'tick' }));
  return api;
}

export const notify = buildAPI(undefined, fixToastOption, eeNotify);

export const alert = buildAPI(undefined, fixToastOption, eeAlert);

// 弹窗组件js接口
const m = {
  vue: undefined,
  i: undefined,
  install(vue) {
    this.vue = vue;
    setTimeout(() => {
      this.i = i;
    })

    vue.config.globalProperties.popup = this;
    vue.config.globalProperties.dialog = dialog;
    vue.config.globalProperties.loading = loading;
    vue.config.globalProperties.toast = toast;
    vue.config.globalProperties.notify = notify;
    vue.config.globalProperties.alert = alert;

    // 注入弹窗组件的渲染
    const render = vue._component.render;
    vue._component.render = function () {
      let rendered = render.apply(vue._component, arguments);
      // P.S. 组件根节点不止一个，渲染时会用一个Fragment(仅渲染内部子组件不渲染自身)节点包住所有子节点
      // 渲染弹窗组件的思路就是将弹窗组件渲染到根节点的子节点内
      // 如果根组件就只有一个根节点且是不能增加子节点的节点例如RouterView时，弹窗组件加入子节点只会导致RouterView不能工作
      // 解决方案：手动渲染一个Fragment
      if (!rendered.children)
        rendered = h(Fragment, [rendered]);
      // 弹窗组件作为#app子组件渲染
      rendered.children.push(h(c));
      return rendered;
    }
  },
  /** 同一类型的组件只能展示一个，第二个展示时，第一个会被关闭
   * @type {popupMethod}
   */
  show(component, options, config) {
    if (component.constructor == String)
      component = this.vue.component(component);
    const exists = i.popups.find(i => i.component.name == component.name);
    if (exists)
      exists.close(true);
    return this.open(component, options, config);
  },
  /** API弹窗
   * @type {popupMethod}
   */
  open(component, options, config) {
    if (component.constructor == String)
      component = this.vue.component(component);
    if (!options) options = {};
    if (!config) config = {};
    // 用了reactive变为响应式，外部修改options里的数据可以直接重新渲染弹窗组件
    options = reactive(options);
    config = reactive(config);
    const popup = {
      component,
      options,
      config,
      close: (immediately) => {
        if (immediately && popup.options.modelValue === true)
          popup.options.modelValue = false;
        this.close(popup);
      },
    };
    i.popups.push(popup);
    return popup;
  },
  /** 关闭一个API弹窗
   * @param {popupInstance} popup - 弹窗实例
   */
  close(popup) {
    if (popup.options.modelValue === true) {
      popup.options.modelValue = false;
    } else {
      const index = i.popups.indexOf(popup);
      if (index != -1) {
        i.popups.splice(index, 1);
        delete popup.instance;
      }
    }
  },
  /** 关闭(一类型的)所有API弹窗
   * @param {string | DefineComponent} component - 需要弹出的组件或组件名，不填则关闭所有弹窗
   */
  closeAll(component) {
    if (component) {
      if (component.constructor == String)
        component = this.vue.component(component);
      i.popups.forEach(i => {
        if (i.component.__file == component.__file)
          this.close(i);
      })
    }
    else
      i.popups.length = 0;
  },
}
export default m;
